﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using EnvDTE;
using EnvDTE80;
using Extensibility;
using Microsoft.VisualStudio.CommandBars;
using Neovolve.Extensibility.VisualStudio.TestProfilerLink.Properties;
using ExtensibilityResources = Neovolve.Extensibility.VisualStudio.Properties.Resources;

namespace Neovolve.Extensibility.VisualStudio.TestProfilerLink
{
    /// <summary>
    /// The <see cref="Connect"/>
    /// class is the entry point for the TestProfilerLink addin.
    /// </summary>
    public class Connect : ConnectBase
    {
        private const String ProfilerCommandPrefix = "ProfilerApplication";

        private const String TestProfilerRefreshCommand = "TestProfilerRefreshConfiguration";

        /// <summary>
        /// Execs the specified command name.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <param name="executeOption">The execute option.</param>
        /// <param name="varIn">The var in.</param>
        /// <param name="varOut">The var out.</param>
        /// <param name="handled">if set to <c>true</c> [handled].</param>
        public override void Exec(
            String commandName,
            vsCommandExecOption executeOption,
            ref Object varIn,
            ref Object varOut,
            ref Boolean handled)
        {
            base.Exec(commandName, executeOption, ref varIn, ref varOut, ref handled);

            try
            {
                if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
                {
                    if (commandName == GetFullCommandName(TestProfilerRefreshCommand))
                    {
                        // Refresh the test frameworks and the profilers configuration
                        Configuration.TestFrameworks.Refresh();
                        Configuration.Profilers.Refresh();

                        // Update the menus
                        DestroyMenus();
                        CreateMenus();
                    }

                    handled = true;
                }
            }
            catch (Exception ex)
            {
                String message = String.Format(
                    CultureInfo.CurrentCulture, ExtensibilityResources.ExecFailureMessageFormat, ex.Message);

                Debug.WriteLine(message);
            }
        }

        /// <summary>
        /// Receives notification that the Add-in is being loaded.
        /// </summary>
        /// <param name="application"></param>
        /// <param name="connectMode"></param>
        /// <param name="addInInst"></param>
        /// <param name="custom"></param>
        /// <seealso class="IDTExtensibility2"/>
        public override void OnConnection(
            Object application, ext_ConnectMode connectMode, Object addInInst, ref Array custom)
        {
            base.OnConnection(application, connectMode, addInInst, ref custom);

            if ((connectMode == ext_ConnectMode.ext_cm_CommandLine) || BuildMode)
            {
                return;
            }

            try
            {
                // Add-in's loaded using add-in manager
                // In this case OnStartupComplete() is not called
                if (connectMode == ext_ConnectMode.ext_cm_AfterStartup)
                {
                    OnStartupComplete(ref custom);
                }
            }
            catch (Exception ex)
            {
                String message = String.Format(
                    CultureInfo.CurrentCulture, ExtensibilityResources.ConnectionFailureMessageFormat, ex.Message);

                Debug.WriteLine(message);
            }
        }

        /// <summary>
        /// Receives notification that the Add-in is being unloaded.
        /// </summary>
        /// <param name="removeMode"></param>
        /// <param name="custom"></param>
        /// <seealso class="IDTExtensibility2"/>
        public override void OnDisconnection(ext_DisconnectMode removeMode, ref Array custom)
        {
            base.OnDisconnection(removeMode, ref custom);

            try
            {
                DestroyMenus();
            }
            catch (Exception ex)
            {
                String message = String.Format(
                    CultureInfo.CurrentCulture, ExtensibilityResources.DisconnectionFailureMessageFormat, ex.Message);

                Debug.WriteLine(message);
            }
        }

        /// <summary>
        /// Receives notification that the host application has completed loading.
        /// </summary>
        /// <param name="custom"></param>
        /// <seealso class="IDTExtensibility2"/>
        public override void OnStartupComplete(ref Array custom)
        {
            if (BuildMode == false)
            {
                try
                {
                    CreateMenus();
                }
                catch (Exception ex)
                {
                    String message = String.Format(
                        CultureInfo.CurrentCulture, ExtensibilityResources.StartupFailureMessageFormat, ex.Message);

                    Debug.WriteLine(message);
                }
            }
        }

        /// <summary>
        /// Queries the status.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <param name="neededText">The needed text.</param>
        /// <param name="status">The status.</param>
        /// <param name="commandText">The command text.</param>
        public override void QueryStatus(
            String commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref Object commandText)
        {
            base.QueryStatus(commandName, neededText, ref status, ref commandText);

            try
            {
                if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
                {
                    Boolean hasActions = true;
                    Boolean hideMenuItemNoActions = false;

                    if (hasActions)
                    {
                        // ReSharper disable BitwiseOperatorOnEnumWihtoutFlags
                        status = vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
                        // ReSharper restore BitwiseOperatorOnEnumWihtoutFlags
                    }
                    else if (hideMenuItemNoActions)
                    {
                        // Don't allow the item to be displayed
                        status = vsCommandStatus.vsCommandStatusInvisible;
                    }
                    else
                    {
                        // Show this item as disabled
                        status = vsCommandStatus.vsCommandStatusSupported;
                    }
                }
                else
                {
                    // ReSharper disable BitwiseOperatorOnEnumWihtoutFlags
                    status = vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
                    // ReSharper restore BitwiseOperatorOnEnumWihtoutFlags
                }
            }
            catch (Exception ex)
            {
                String message = String.Format(
                    CultureInfo.CurrentCulture, ExtensibilityResources.QueryStatusFailureMessageFormat, ex.Message);

                Debug.WriteLine(message);
            }
        }

        private void CreateMenus()
        {
            CreateTestProfilerMenuItem();
        }

        /// <summary>
        /// Creates the test profiler menu item.
        /// </summary>
        private void CreateTestProfilerMenuItem()
        {
            // Check if the menu item already exists
            if (TestProfilerMenuItem != null)
            {
                return;
            }

            CommandBar menuBar = GetCommandBar(ObjectName.MenuBar);
            CommandBarPopup menuPopup = GetMenuPopup(ObjectName.Tools, menuBar);

            // Create the popup menu
            TestProfilerMenuItem =
                menuPopup.Controls.Add(MsoControlType.msoControlPopup, Type.Missing, Type.Missing, 1, true) as
                CommandBarPopup;
            TestProfilerMenuItem.CommandBar.Name = menuPopup.CommandBar.Name + "TestProfilerLink";
            TestProfilerMenuItem.Caption = Resources.TestProfilerMenuName;
            TestProfilerMenuItem.TooltipText = Resources.TestProfilerMenuDescription;
            TestProfilerMenuItem.Visible = true;

            // Check if there is more than 1 item in the menu
            if (menuPopup.Controls.Count > 1)
            {
                // Get the next item in the menu to include a separator
                menuPopup.Controls[2].BeginGroup = true;
            }

            // Create the child menu items

            Int32 profilerCount = 0;

            // Loop through each configured profiler
            for (Int32 index = 0; index < Configuration.Profilers.Items.Count; index++)
            {
                // Get the profiler
                Profiler item = Configuration.Profilers.Items[index];

                // Check that the profiler exists
                if (File.Exists(item.ApplicationPath))
                {
                    // We have a profiling application that we can use
                    profilerCount++;

                    // Create the command for the profiler
                    Command profilerCommand = CreateCommand(
                        ProfilerCommandPrefix + item.CommandName, item.Name, item.Description, 1);

                    // Add the command to the popup menu
                    CommandBarButton buttonCommand =
                        profilerCommand.AddControl(TestProfilerMenuItem.CommandBar, profilerCount) as CommandBarButton;

                    // Check that we have a button to work with
                    if (buttonCommand != null)
                    {
                        // Workaround: The button created from the command displays the incorrect text
                        // This logic will ensure that the button contains the correct value
                        String commandText = buttonCommand.Caption;

                        // Check if the button contains the wrong caption
                        if (commandText != item.Name)
                        {
                            // Reassign the text to the button
                            buttonCommand.Caption = item.Name;

                            ((Commands2)AddInInstance.DTE.Commands).UpdateCommandUI(true);
                        }

                        if (item.ExtractIcon)
                        {
                            try
                            {
                                // Get the picture for the profiler
                                Icon profilerIcon = Icon.ExtractAssociatedIcon(item.ApplicationPath);

                                if (profilerIcon != null)
                                {
                                    buttonCommand.Picture = ConvertImage.Convert(profilerIcon);
                                }
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(
                                    "Failed to extract and assign application icon" + Environment.NewLine + ex);
                            }
                        }
                    }
                }
            }

            // Create a refresh menu item
            Command refreshCommand = CreateCommand(
                TestProfilerRefreshCommand,
                Resources.RefreshConfigurationMenuName,
                Resources.RefreshConfigurationMenuDescription,
                3);
            refreshCommand.AddControl(TestProfilerMenuItem.CommandBar, profilerCount + 1);

            if (profilerCount > 0)
            {
                // Add a separator before the Refresh menu item
                TestProfilerMenuItem.Controls[TestProfilerMenuItem.Controls.Count].BeginGroup = true;
            }
        }

        /// <summary>
        /// Destroys the menus.
        /// </summary>
        private void DestroyMenus()
        {
            if (TestProfilerMenuItem != null)
            {
                TestProfilerMenuItem.Delete(null);

                TestProfilerMenuItem = null;
            }
        }

        /// <summary>
        /// Gets or sets the test profiler menu item.
        /// </summary>
        /// <value>The test profiler menu item.</value>
        private CommandBarPopup TestProfilerMenuItem
        {
            get;
            set;
        }

        //        ---------------------------
        //JetBrains dotTrace
        //---------------------------
        //JetBrains dotTrace 3.1 build 355.155 (built on 5/05/2008 5:17:24 PM)
        //Copyright (C) 2005-2007 JetBrains s.r.o. All rights reserved.
        //Usage:
        //  JetBrains.dotTrace.exe
        //    -help |
        //    ([-open [-temporary|-permanent]] <snapshot>) |
        //    (-profile cpu [-manual|-immediately] [-output <snapshot>]
        //      (-web <timeout>) |
        //      (-service <service name> <timeout> [<appargument #0> [<appargument #1> ...]]) |
        //      (-application [-hide|-show] [-directory <appdirectory>] <appfilename> [<appargument #0> [<appargument #1> ...]])) |
        //    (-report <reportparams> <snapshot> <reportoutput>)
        //-help (-?) : Show help window.
        //-profile (-prof) : Profile ASP.NET or .NET application.
        //-immediately (-imm) : Start profiling immediately (default).
        //-manual (-man) : Do not start profilee immediately.
        //-output (-out) : Filename to save snapshot to.
        //-web : Profile ASP.NET application for given period of time. By default snapshot filename is web0.jbprof, web1.jbprof, ...
        //-service (-svc) : Profile Windows service for given period of time. By default snapshot filename is svc0.jbprof, svc1.jbprof, ...
        //-application (-app) : Profile Windows application. By default snapshot filename is <appfilename>0.jbprof, <appfilename>1.jbprof, ...
        //-hide : Hide console.
        //-show : Show console (default).
        //-directory (-dir) : Set application working directory.
        //-open : Open specified snapshot in UI.
        //-permanent (-perm) : Open specified snapshot (default).
        //-temporary (-temp) : Open specified snapshot and remove it if user do not save it.
        //-report (-rep) : Generate report from a snapshot file. Report options are specified in XML file.

        //        Microsoft (R) Test Execution Command Line Tool Version 9.0.21022.8
        //Copyright (c) Microsoft Corporation. All rights reserved.

        //Usage:                              MSTest.exe [options].

        //Description:                        Run tests in test files or in a metadata
        //                                    file. Optionally, publish test results,
        //                                    if Team Explorer is also installed.
        //Options:
        //  /help                             Display this usage message.
        //                                    (Short form: /? or /h)
        //  /nologo                           Do not display the startup banner and
        //                                    copyright message.
        //  /testcontainer:[file name]        Load a file that contains tests. You can
        //                                    Specify this option more than once to
        //                                    load multiple test files.
        //                                    Examples:
        //                                      /testcontainer:mytestproject.dll
        //                                      /testcontainer:loadtest1.loadtest
        //  /testmetadata:[file name]         Load a metadata file.
        //                                    Example:
        //                                      /testmetadata:testproject1.vsmdi
        //  /runconfig:[file name]            Use the specified run configuration file.
        //                                    Example:
        //                                      /runconfig:mysettings.testrunconfig
        //  /resultsfile:[file name]          Save the test run results to the specified
        //                                    file.
        //                                    Example:
        //                                      /resultsfile:c:\temp\myresults.trx
        //  /testlist:[test list path]        The test list, as specified in the metadata
        //                                    file, to be run. You can specify this
        //                                    option multiple times to run more than
        //                                    one test list.
        //                                    Example:
        //                                      /testlist:checkintests/clientteam
        //  /test:[test name]                 The name of a test to be run. You can
        //                                    specify this option multiple times to run
        //                                    more than one test.
        //  /unique                           Run a test only if one unique match is
        //                                    found for any given /test.
        //  /noisolation                      Run tests within the MSTest.exe process.
        //                                    This choice improves test run speed but
        //                                    increases risk to the MSTest.exe process.
        //  /noresults                        Do not save the test results in a TRX file.
        //                                    This choice improves test run speed but
        //                                    does not save the test run results.
        //  /detail:[property id]             The name of a property that you want to
        //                                    show values for, in addition to the test
        //                                    outcome. Please examine a test results
        //                                    file to see the available properties.
        //                                    Example: /detail:errormessage
        //The following options are also available if Team Explorer is installed:
        //  /publish:[server name]            Publish results to the Team Foundation
        //                                    Server.
        //  /publishbuild:[build name]        The build identifier to be used to publish
        //                                    test results.
        //  /publishresultsfile:[file name]   The name of the test results file to
        //                                    publish. If none is specified, use the
        //                                    file produced by the current test run.
        //  /teamproject:[team project name]  The name of the team project to which the
        //                                    build belongs. Specify this when publishing
        //                                    test results.
        //  /platform:[platform]              The platform of the build against which
        //                                    to publish test results.
        //  /flavor:[flavor]                  The flavor of the build against which
        //                                    to publish test results.
    }
}